//
//  SoundManager v 1.2.1 - soundManager package
//  Russell Lowke, June 1st 2011
// 
//  Copyright (c) 2008-2011 Lowke Media
//  see http://www.lowkemedia.com for more information
//  see http://code.google.com/p/lowke/ for code repository
//
//  Permission is hereby granted, free of charge, to any person obtaining a 
//  copy of this software and associated documentation files (the "Software"), 
//  to deal in the Software without restriction, including without limitation 
//  the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//  and/or sell copies of the Software, and to permit persons to whom the 
//  Software is furnished to do so, subject to the following conditions:
// 
//  The above copyright notice and this permission notice shall be included in 
//  all copies or substantial portions of the Software.
// 
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER  
//  DEALINGS IN THE SOFTWARE. 
//
//

//
// 
//  When possible encode audio at 44.1KHz Stereo.
//  This is because it is the target playback sampling rate of the Flash Player, 
//  so if it’s not encoded at that then the player will have to to re-sample and 
//  filter your audio which takes away precious CPU cycles.
//  Audio data is always exposed as 44100 Hz Stereo.


package com.lowke.soundManager
{
    import com.lowke.assetLoader.AssetLoader;
    import com.lowke.assetManager.cache.Cache;
    import com.lowke.assetManager.groupAssetManager.AssetGroup;
    import com.lowke.assetManager.groupAssetManager.GroupAssetManager;
    import com.lowke.logAs.LogAs;
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    
    public class SoundManager extends EventDispatcher 
    {   
        // dispatched events
        public static const COMPLETE:String = "soundManager_complete";
        
        //
        // error, warning and ifo IDs
        public static const WARNING_SOUND_NOT_CACHED:String = "SND00";
        public static const DEBUG_SOUND_AT_MAX_PLAYS:String = "SND01";
        
        private var _mgr:GroupAssetManager;
        
        public function SoundManager() 
        {
            super();
            _mgr = new GroupAssetManager(new Cache());
        }
        
        public function parseSoundsXml(xml:XML, root:String = ""):void 
        {
            // load sounds
            for each(var soundXML:XML in xml["Sound"]) 
            {
                var volume:Number = Number(soundXML["vol"]);                                       // <vol> tag
                var stagger:uint = uint(soundXML["stagger"]);                                      // <stagger> tag
                var maxConcurrent:uint = uint(soundXML["maxConcurrent"]);                          // <maxConcurrent> tag
                var loopStr:String = soundXML["loop"];                                             // <loop> tag
                loopStr.toLowerCase();
                var loop:Boolean = (loopStr == "true") ? true : false;
                var lazyStr:String = soundXML["lazy"];                                             // <lazy> tag
                lazyStr.toLowerCase();
                var lazy:Boolean = (lazyStr == "true") ? true : false;
                if (lazy) 
                {
                    loadLazySound(root + soundXML["url"], soundXML.@id, volume, stagger, loop, maxConcurrent); 
                } 
                else 
                {                                                                        // <url> tag
                    loadSound(root + soundXML["url"], soundXML.@id, volume, stagger, loop, maxConcurrent);
                }
            }
            
            // load types
            for each (var typeXML:XML in xml["Type"]) 
            {
                var id:String = typeXML.@id;
                var assetIDs:Array = new Array();
                for each (var assetIdXML:XML in typeXML["assetId"]) 
                {
                    var assetId:String = assetIdXML;
                    assetIDs.push(assetId);
                }
                _mgr.cacheGroupWrapper(id, assetIDs);
            }
            
            _mgr.whenDone(ready);
        }
        
        private function ready():void 
        {
            this.dispatchEvent(new Event(COMPLETE));
        }
        
        // "lazy" sounds only load and cache when 1st played.
        //  this causes considerable delay before the sound plays for the 1st time,
        //  but prevents unused "lazy" sounds from taking up memory
        public function loadLazySound(url:String, 
                                      id:String, 
                                      defaultVolume:Number = 1, 
                                      defaultDelay:Number = 0, 
                                      loopByDefault:Boolean = false,
                                      maxConcurrent:uint = 0):void 
        {
            // store sound information in LazySoundData object
            var loadLazySound:LazySoundData = new LazySoundData(url, id, defaultVolume, defaultDelay, loopByDefault, maxConcurrent);
            _mgr.cache.cacheAsset(loadLazySound, id, false);
        }
        
        
        public function loadSound(url:String, id:String, 
                                  defaultVolume:Number = 1, 
                                  defaultDelay:Number = 0, 
                                  loopByDefault:Boolean = false,
                                  maxConcurrent:uint = 0):void 
        {
            var loadSoundFx:LoadSoundFx = new LoadSoundFx(url);
            _mgr.loadUsing(loadSoundFx, addSound, id, defaultVolume, defaultDelay, loopByDefault, maxConcurrent, url);
        }
        
        public function addSound(snd:SoundFx,
                                 id:String,
                                 defaultVolume:Number = 1,
                                 defaultRandomStagger:uint = 0,
                                 loopByDefault:Boolean = false,
                                 maxConcurrent:uint = 0,
                                 url:String = null):void {
            
            if (snd) 
            {
                snd.defaultVolume = defaultVolume;
                snd.defaultRandomStagger = defaultRandomStagger;
                snd.loopByDefault = loopByDefault;
                snd.maxConcurrent = maxConcurrent;
                snd.id = id;
            }
            _mgr.cache.cacheAsset(snd, id, false, url, null, true, false);
        }
        
        public function addType(id:String, assetIDs:Array, replace:Boolean = false):void 
        {
            _mgr.cacheGroupWrapper(id, assetIDs, replace);
        }
        
        
        // unloadSound is really the same as forget()
        public function unloadSound(id:String, giveWarning:Boolean = true):void 
        {
            forget(id, giveWarning);
        }
        
        public function play(id:String,
                             volume:Number = 1,
                             randomStagger:uint = 0,
                             funct:Function = null, 
                             ... args):void 
        {
            var asset:* = retrieve(id);
            if (! asset) 
            {
                // no sound found
                LogAs.warning("Could not play sound \"" + id + "\" as it has not been cached yet.", WARNING_SOUND_NOT_CACHED);
                return;
            } 
            else if (asset is LazySoundData) 
            {
                // load lazy sound
                var lazy:LazySoundData = asset as LazySoundData;
                var loadSoundFx:LoadSoundFx = new LoadSoundFx(lazy.url);
                var ldr:AssetLoader = AssetLoader.instance;
                ldr.loadUsing(loadSoundFx, 
                    lazyLoaded,             // callback function
                    lazy.id, 
                    lazy.defaultVolume, 
                    lazy.defaultRandomStagger, 
                    lazy.loopByDefault, 
                    lazy.maxConcurrent,
                    volume);
                return;
            }
            
            var sfx:SoundFx = asset as SoundFx;
            sfx.playFX.apply(null, [volume, randomStagger, funct].concat(args));
        }
        
        
        public function stop(id:String):void 
        {
            // if an asset group then get id of last asset given
            var assetWrapper:* = cache.retrieveWrapper(id);
            if (assetWrapper is AssetGroup) 
            {
                id = (assetWrapper as AssetGroup).lastID;
                if (! id) 
                {
                    return;
                }
            }
            
            var asset:* = cache.retrieve(id);
            if (asset is SoundFx) 
            {
                (asset as SoundFx).stop();
            }
        }
        
        private function lazyLoaded(snd:SoundFx,
                                    id:String,
                                    defaultVolume:Number = 1, 
                                    defaultRandomStagger:uint = 0,
                                    loopByDefault:Boolean = false, 
                                    maxConcurrent:uint = 0,
                                    volume:Number = 1):void 
        {
            
            forget(id);     // remove the stub LazySoundData class
            addSound(snd, id, defaultVolume, defaultRandomStagger, loopByDefault, maxConcurrent);
            play(id, volume);
        }
        
        //
        // passthrough methods
        
        public function retrieve(id:String, giveWarning:Boolean = true):*
        {
            return _mgr.cache.retrieve(id, giveWarning);
        }
        
        public function forget(id:String, giveWarning:Boolean = true):void
        {
            _mgr.cache.forget(id, giveWarning);
        }
        
        public function get cache():Cache
        {
            return _mgr.cache;
        }
    }
}
